/**
 * @fileOverview Cube core Event
 * @author <a href="mailto:simonsliu@tencent.com">simonsliu</a>
 * @date 11-12-15 - 下午5:51
 */
(function() {
	function returnFalse() {
		return false;
	}

	function returnTrue() {
		return true;
	}

	/**
	 * @class
	 * @requires Cube.Element
	 */
	Cube.Event = function( src, props ) {
		// Allow instantiation without the 'new' keyword
		if ( !this.preventDefault ) {
			return new Cube.Event( src, props );
		}

		// Event object
		if ( src && src.type ) {
			this.originalEvent = src;
			this.type = src.type;

			// Events bubbling up the document may have been marked as prevented
			// by a handler lower down the tree; reflect the correct value.
			this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false ||
				src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse;

		// Event type
		} else {
			this.type = src;
		}

		// Put explicitly provided properties onto the event object
		if ( props ) {
			Cube.merge( this, props );
		}

		// timeStamp is buggy for some events on Firefox(#3843)
		// So we won't rely on the native value
		this.timeStamp = Date.now();

		// Mark it as fixed
		this.fixed = true;
	};
	Cube.Event.prototype = {
		preventDefault: function() {
			this.isDefaultPrevented = returnTrue;

			var e = this.originalEvent;
			if ( !e ) {
				return;
			}

			// if preventDefault exists run it on the original event
			if ( e.preventDefault ) {
				e.preventDefault();

			// otherwise set the returnValue property of the original event to false (IE)
			} else {
				e.returnValue = false;
			}
		},
		stopPropagation: function() {
			this.isPropagationStopped = returnTrue;

			var e = this.originalEvent;
			if ( !e ) {
				return;
			}
			// if stopPropagation exists run it on the original event
			if ( e.stopPropagation ) {
				e.stopPropagation();
			}
			// otherwise set the cancelBubble property of the original event to true (IE)
			e.cancelBubble = true;
		},
		stopImmediatePropagation: function() {
			this.isImmediatePropagationStopped = returnTrue;
			this.stopPropagation();
		},
		isDefaultPrevented: returnFalse,
		isPropagationStopped: returnFalse,
		isImmediatePropagationStopped: returnFalse
	}
	/** @lends Cube.Event */
	Cube.merge(Cube.Event, {
		add: function(elem, types, handler, data) {
			if (elem.nodeType === 3 || elem.nodeType === 8) {
				return;
			}

			if (handler === false) {
				handler = returnFalse;
			} else if (!handler) {
				return;
			}

			var handleObjIn, handleObj;

			if (handler.handler) {
				handleObjIn = handler;
				handler = handleObjIn.handler;
			}

			// Make sure that the function being executed has a unique ID
			if (!handler.guid) {
				handler.guid = Cube.guid++;
			}

			// Init the element's event structure
			var elemData = Cube.Element._data(elem);

			// If no elemData is found then we must be trying to bind to one of the
			// banned noData elements
			if (!elemData) {
				return;
			}

			var events = elemData.events,
				eventHandle = elemData.handle;

			if (!events) {
				elemData.events = events = {};
			}

			if (!eventHandle) {
				elemData.handle = eventHandle = function(e) {
					console.log(e, Cube.Event.triggered);
					// Discard the second event of a jQuery.event.trigger() and
					// when an event is called after a page has unloaded
					return typeof Cube !== "undefined" && (!e || Cube.Event.triggered !== e.type) ?
							Cube.Event.handle.apply(eventHandle.elem, arguments) :
							undefined;
				};
			}
			
			// Add elem as a property of the handle function
			// This is to prevent a memory leak with non-native events in IE.
			eventHandle.elem = elem;

			// Handle multiple events separated by a space
			// jQuery(...).bind("mouseover mouseout", fn);
			types = types.split(" ");

			var type, i = 0, namespaces;

			while ((type = types[ i++ ])) {
				handleObj = handleObjIn ?
						Cube.merge({}, handleObjIn) :
				{ handler: handler, data: data };

				// Namespaced event handlers
				if (type.indexOf(".") > -1) {
					namespaces = type.split(".");
					type = namespaces.shift();
					handleObj.namespace = namespaces.slice(0).sort().join(".");

				} else {
					namespaces = [];
					handleObj.namespace = "";
				}

				handleObj.type = type;
				if (!handleObj.guid) {
					handleObj.guid = handler.guid;
				}

				// Get the current list of functions bound to this event
				var handlers = events[ type ],
					special = Cube.Event.special[ type ] || {};

				// Init the event handler queue
				if (!handlers) {
					handlers = events[ type ] = [];

					// Check for a special event handler
					// Only use addEventListener/attachEvent if the special
					// events handler returns false
					if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) {
						elem.addEventListener(type, eventHandle, false);
					}
				}

				if (special.add) {
					special.add.call(elem, handleObj);

					if (!handleObj.handler.guid) {
						handleObj.handler.guid = handler.guid;
					}
				}

				// Add the function to the element's handler list
				handlers.push(handleObj);
				console.log( 'handlers: ', handlers, handleObj, elemData, Cube.Element._data( elem ) );

				// Keep track of which events have been used, for event optimization
				Cube.Event.global[ type ] = true;
			}
		},
		remove: function( elem, types, handler, pos ) {
			// don't do events on text and comment nodes
			if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
				return;
			}

			if ( handler === false ) {
				handler = returnFalse;
			}

			var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
				elemData = Cube.Element.hasData( elem ) && Cube.Element._data( elem ),
				events = elemData && elemData.events;

			if ( !elemData || !events ) {
				return;
			}

			// types is actually an event object here
			if ( types && types.type ) {
				handler = types.handler;
				types = types.type;
			}

			// Unbind all events for the element
			if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
				types = types || "";

				for ( type in events ) {
					Cube.Event.remove( elem, type + types );
				}

				return;
			}

			// Handle multiple events separated by a space
			// jQuery(...).unbind("mouseover mouseout", fn);
			types = types.split(" ");

			while ( (type = types[ i++ ]) ) {
				origType = type;
				handleObj = null;
				all = type.indexOf(".") < 0;
				namespaces = [];

				if ( !all ) {
					// Namespaced event handlers
					namespaces = type.split(".");
					type = namespaces.shift();

					namespace = new RegExp("(^|\\.)" +
						Cube.Element.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)");
				}

				eventType = events[ type ];

				if ( !eventType ) {
					continue;
				}

				if ( !handler ) {
					for ( j = 0; j < eventType.length; j++ ) {
						handleObj = eventType[ j ];

						if ( all || namespace.test( handleObj.namespace ) ) {
							Cube.Element.remove( elem, origType, handleObj.handler, j );
							eventType.splice( j--, 1 );
						}
					}

					continue;
				}

				special = Cube.Element.special[ type ] || {};

				for ( j = pos || 0; j < eventType.length; j++ ) {
					handleObj = eventType[ j ];

					if ( handler.guid === handleObj.guid ) {
						// remove the given handler for the given type
						if ( all || namespace.test( handleObj.namespace ) ) {
							if ( pos == null ) {
								eventType.splice( j--, 1 );
							}

							if ( special.remove ) {
								special.remove.call( elem, handleObj );
							}
						}

						if ( pos != null ) {
							break;
						}
					}
				}

				// remove generic event handler if no more handlers exist
				if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
					if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
						elem.removeEventListener( type, elemData.handle );
					}

					ret = null;
					delete events[ type ];
				}
			}

			// Remove the expando if it's no longer used
			if ( Cube.isEmptyObject( events ) ) {
				var handle = elemData.handle;
				if ( handle ) {
					handle.elem = null;
				}

				delete elemData.events;
				delete elemData.handle;

				if ( Cube.isEmptyObject( elemData ) ) {
					Cube.Element.removeData( elem, undefined, true );
				}
			}
		},
		handle: function( event ) {
			event = Cube.Event.fix( event || window.event );
			// Snapshot the handlers list since a called handler may add/remove events.
			var handlers = ((Cube.Element._data( this, "events" ) || {})[ event.type ] || []).slice(0),
				run_all = !event.exclusive && !event.namespace,
				args = Array.prototype.slice.call( arguments, 0 );

			// Use the fix-ed Event rather than the (read-only) native event
			args[0] = event;
			event.currentTarget = this;

			for ( var j = 0, l = handlers.length; j < l; j++ ) {
				var handleObj = handlers[ j ];

				// Triggered event must 1) be non-exclusive and have no namespace, or
				// 2) have namespace(s) a subset or equal to those in the bound event.
				if ( run_all || event.namespace_re.test( handleObj.namespace ) ) {
					// Pass in a reference to the handler function itself
					// So that we can later remove it
					event.handler = handleObj.handler;
					event.data = handleObj.data;
					event.handleObj = handleObj;

					var ret = handleObj.handler.apply( this, args );

					if ( ret !== undefined ) {
						event.result = ret;
						if ( ret === false ) {
							event.preventDefault();
							event.stopPropagation();
						}
					}

					if ( event.isImmediatePropagationStopped() ) {
						break;
					}
				}
			}
			return event.result;
		},
		fix: function( event ) {
			if ( event.fixed === true ) {
				return event;
			}

			// store a copy of the original event object
			// and "clone" to set read-only properties
			var originalEvent = event;
			event = Cube.Event( originalEvent );

			for ( var i = this.props.length, prop; i; ) {
				prop = this.props[ --i ];
				event[ prop ] = originalEvent[ prop ];
			}

			// Fix target property, if necessary
			if ( !event.target ) {
				// Fixes #1925 where srcElement might not be defined either
				event.target = event.srcElement || document;
			}

			// check if target is a textnode (safari)
			if ( event.target.nodeType === 3 ) {
				event.target = event.target.parentNode;
			}

			// Add relatedTarget, if necessary
			if ( !event.relatedTarget && event.fromElement ) {
				event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
			}

			// Calculate pageX/Y if missing and clientX/Y available
			if ( event.pageX == null && event.clientX != null ) {
				var eventDocument = event.target.ownerDocument || document,
					doc = eventDocument.documentElement,
					body = eventDocument.body;

				event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
				event.pageY = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
			}

			// Add which for key events
			if ( event.which == null && (event.charCode != null || event.keyCode != null) ) {
				event.which = event.charCode != null ? event.charCode : event.keyCode;
			}

			// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
			if ( !event.metaKey && event.ctrlKey ) {
				event.metaKey = event.ctrlKey;
			}

			// Add which for click: 1 === left; 2 === middle; 3 === right
			// Note: button is not normalized, so don't use it
			if ( !event.which && event.button !== undefined ) {
				event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
			}

			return event;
		},
		props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
		global: {},
		special: {
			/*ready: {
				// Make sure the ready event is setup
				setup: jQuery.bindReady,
				teardown: jQuery.noop
			},

			live: {
				add: function( handleObj ) {
					jQuery.event.add( this,
						liveConvert( handleObj.origType, handleObj.selector ),
						jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) );
				},

				remove: function( handleObj ) {
					jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj );
				}
			},

			beforeunload: {
				setup: function( data, namespaces, eventHandle ) {
					// We only want to do this special case on windows
					if ( jQuery.isWindow( this ) ) {
						this.onbeforeunload = eventHandle;
					}
				},

				teardown: function( namespaces, eventHandle ) {
					if ( this.onbeforeunload === eventHandle ) {
						this.onbeforeunload = null;
					}
				}
			}*/
		}
	});
})()
